/* 

==========================================================

DX490a - Summer 2010

Instructor: Stelios Manousakis

==========================================================

Class 3.1:

Timing in SuperCollider

Contents:

• Clocks

- SystemClock

- TempoClock

- AppClock

==========================================================

*/



// ================= CLOCKS =================

// In order to time processes, you need a class that can parse time: a Clock

// There are three different clocks in SC.


// ====== SystemClock ======

// In SC there is a global clock, called SystemClock. This is the most accurate option if you want to measure time in seconds. Notice that SystemClock is global, or a singleton; that is, only one instance can exist at a time inside SC (which makes a lot of sense since it's the 'system' clock)


// let's create a syntdef to use in this entire file:

s.boot;

(

SynthDef(\gray, {arg outBus = 0, freq = 400, amp = 1, dur = 1;

var env, src, fdbin, fdbout;

env = EnvGen.kr(Env([0, 1, 0], [0.05, 0.95], \sin), timeScale: dur, levelScale: amp, doneAction: 2);

src = LPF.ar(GrayNoise.ar(amp), freq, env);

Out.ar(outBus, Pan2.ar(src, Rand.new(-0.7, 0.7)));

}).load(s);

)


// now, play it once after half a second using the system clock. 

(

SystemClock.sched(

0.5, // time delay from evaluation  

{Synth(\gray)} // what you want your clock to do; Attention: Must be a function!

);

)


// now let's create a few repeating noise bursts:

(

SystemClock.sched(

0.5, // time delay from evaluation  

{Synth(\gray); // function for the clock to perform

0.75} // repeat every 0.75 secs

);

)

// you can manually stop it with Command-period, or:

SystemClock.clear; 

// Yes, either way will clear ALL running SystemClocks...



// let's try to make it a bit more interesting

(

b = Array.series(7, 0.125, 0.125).reverse; // use this to create a bit more complex timing 

SystemClock.sched(

0.5, // time delay from evaluation  

{Synth(\gray, ([\freq, 1200.rrand(400) , \amp, 1.rrand(0.5)])); // function for the clock to perform, made slightly more interesting

b.choose}  // pick any value from that array

);

)


// now let's trigger a 10 second gesture  by pressing spacebad

(

b = Array.series(7, 0.125, 0.125).reverse; // use this to create a bit more complex timing 

w = SCWindow.new("press space to start a 10sec gesture");

w.view.keyDownAction = { arg view, char, modifiers, unicode, keycode;

if (unicode == 32, {

t = Main.elapsedTime; 

"elapsed time is: ".post; t.postln;

SystemClock.sched(

1, // time delay from evaluation  

{Synth(\gray, ([\freq, 1200.rrand(400) , \amp, 1.rrand(0.5)])); // function for the clock to perform, made slightly more interesting

b.choose}  // pick any value from that array

);

SystemClock.sched(11, {SystemClock.clear}) // after 10 seconds, stop the clock

})};

w.front;

)


// The .sched method uses relative timing, counting from when the code was evaluate

// But, you can also time things to happen in a specific time after SC loads, using the .schedAbs method. The following example gives the same exact results, but with a different method:


(

b = Array.series(7, 0.125, 0.125).reverse; // use this to create a bit more complex timing 

w = SCWindow.new("press space to start a 10sec gesture");

w.view.keyDownAction = { arg view, char, modifiers, unicode, keycode;

if (unicode == 32, {

t = Main.elapsedTime; 

("SC has been open for "++ t.asString++" seconds").postln;

SystemClock.schedAbs(

t +1, // time delay from evaluation  

{Synth(\gray, ([\freq, 1200.rrand(400) , \amp, 1.rrand(0.5)])); // function for the clock to perform, made slightly more interesting

b.choose}  // pick any value from that array

);

SystemClock.schedAbs(t + 11, {SystemClock.clear})

})};

w.front;

)



// ====== TempoClock ======

// Another option for timing is to use TempoClock. This clock measures time in beats-per-second (not beats-per-minute) and is as stable as the SystemClock; besides tempo related time, it can give you the elapsed time too. It is also NOT a singleton, so you can have many instances running at the same time - and you can stop each one individually. It has many more methods than SystemClock.


// Here's an example we saw with SystemClock, but using TempoClock instead:


(

c = TempoClock(1.5); // create a new clock with 1.5 beats-per-second, ie: 1.5 * 60 = 90bpm

c.schedAbs(

1, // time delay from evaluation (in beats)

{arg ...args;

[c.bar, args[0], args[1]].postln; // post current bar, current beat nr and elapsed time

Synth(\gray); // make some sound

2.0}); // repeat every second beat

)

c.tempo_(3); // double the tempo

c.elapsedBeats.postln; // you can also ask what the current beat nr is

c.elapsedBeats.ceil; // or get the next beat

c.stop; //stop the clock



// some polyrthythm: 4/4 on 7/4 

(

c = TempoClock(6);

c.schedAbs(1, {arg beat, sec;

if (beat % 4 == 0, {Synth(\gray, ([freq:5000.rrand(8000), amp:0.25]))});

if (beat % 4 == 2, {Synth(\gray, ([freq:4000.rrand(5000), amp:0.3]))});

if (beat % 7 == 0, {Synth(\gray, ([freq:400.rrand(800), amp:0.6]))});

if (beat % 7 == 2, {Synth(\gray, ([freq:800.rrand(1000), amp:0.55]))});

if (beat % 7 == 4, {Synth(\gray, ([freq:1000.rrand(3000), amp:0.35]))});

0.5}); 

)

c.tempo_(3)

c.stop

// You can have two clocks running at the same time.

(

m = Main.elapsedTime; // use this to synchronize them

c = TempoClock(2, 0, m + 1); // create a new clock with 1.5 beats-per-second, ie: 1.5 * 60 = 90bpm

c.schedAbs( 0, // time delay from evaluation (in beats)

{arg ...args;

"clock one is here: ".post;

[c.bar, args[0], args[1]].postln; // post current bar, current beat nr and elapsed time

Synth(\gray, ([freq:500, amp:0.6])); // make some sound

2.0}); // repeat every second beat

d = TempoClock(3.5, 0, m + 1); // create a new clock with 1.5 beats-per-second, ie: 1.5 * 60 = 90bpm

d.schedAbs( 1, // time delay from evaluation (in beats)

{arg ...args;

"clock two is here: ".post;

[c.bar, args[0], args[1]].postln; // post current bar, current beat nr and elapsed time

Synth(\gray, ([freq:5000, amp:0.25])); // make some sound

1.0}); // repeat every second beat

)



// ====== AppClock ======


// Finally, AppClock is another singleton clock, very similar in functionality to SystemClock - but less reliable because it runs on a lower priority thread. This is because AppClock is the clock to use for dealing with graphics, which in SuperCollider 3 have to run on a lower priority. 

// If you do something and get a post like "cannot be called from this process", you need to replace the clock you're using with the AppClock, OR put your GUI code inside a function running on a lower priority thread by using the .defer method:

{//GUI code

}.defer

// This will actually reassign the function to the AppClock for you behind the scenes. So this:

(

var w, r;

w = Window.new("trem", Rect(200, 200, Window.screenBounds.width * 0.5, Window.screenBounds.height * 0.5));

w.front;

r = Routine({ arg time;

60.do({ arg i;

0.05.yield;

w.bounds = w.bounds.moveBy(10.rand2, 10.rand2);

w.alpha = cos(i*0.1pi)*0.5+0.5;

});

1.yield;

w.close;

});

AppClock.play(r);

)

// Is the same as this:

(

var w, r;

w = Window.new("trem", Rect(200, 200, Window.screenBounds.width * 0.5, Window.screenBounds.height * 0.5));

w.front;

r = Routine({ arg time;

60.do({ arg i;

0.05.yield;

{// must enclose this in a defered loop!

w.bounds = w.bounds.moveBy(10.rand2, 10.rand2);

w.alpha = cos(i*0.1pi)*0.5+0.5;

}.defer; // Notice the .defer here - otherwise it won't work!

});

1.yield;

w.close;

});

SystemClock.play(r);

)